package com.strongmangames.DPM;

/**
 * HashCode :	A class for generating hash codes from arbitrary objects.
 * 
 * @langversion		HaXe 2
 * @targets			!cpp, flash, flash9, js, neko, php
 * @author			Justin Donaldson
 * @since			2010-02-05
 */
class HashCode
{
	#if flash9
	private static	var dictionary = new flash.utils.TypedDictionary<Dynamic,Int>(true);
	#end
	private static var nullSalt:Int = random();
	private static var objCounter:Int = nullSalt + 1;

/**
 *  The main public function for the class.  Returns an integer ID for any object. 
 *  These are not guaranteed to be unique, but are highly unlikely to collide.
 *	@param o Dynamic the object to be hashed
 *	@return Int the hash code
 */
	public static function getHash(o:Dynamic):Int{
	
		switch(Type.typeof(o)){
			case TBool : return hashBool(o);
			case TInt : return o;
			case TClass(c) : {				
				switch(Type.getClassName(c)){
					case "String" : return untyped hashString(o);
					default : return hashObject(o);
				}
			}
			case TFloat : return hashString(Std.string(o));
			case TObject : return hashObject(o);
			case TNull : return nullSalt;
			case TUnknown : {throw("Can't hash an unknown type"); return nullSalt;}
			case TFunction : {throw("Can't hash a function"); return nullSalt;}
			case TEnum(e) : {throw("Can't hash an Enum"); return nullSalt;}
		}
		
	}

/**
 *  Routine to retrieve integer hash codes from objects.
 *	@param o Dynamic the object that needs a hash code.
 *	@return Int the hash code.
 */
	private static function hashObject(o:Dynamic) : Int {
		if(Reflect.hasField(o,'__id')) {
			return Reflect.field(o,'__id');
		}
		else{
			#if flash9
				if (dictionary.exists(o)) return dictionary.get(o);
				else {				
					dictionary.set(o,++objCounter);
					return objCounter;
				}
			#elseif php
				return untyped hashString(__call__('spl_object_hash',o));
/*			#elseif neko
				return untyped __dollar__hkey(o);*/
			#else
			Reflect.setField(o,'__id',++objCounter);
			return objCounter;
			#end
		}
	

	}

	
	
/**
 *  Simple random function that uses the full platform integer range.
 *	@private
 *	@return Int
 */
	private static function random():Int{
		#if neko
			var max_rand = Std.int(Math.pow(2,30)-1);
		#else
			var max_rand = Std.int(Math.pow(2,31)-1);
		#end
		return Std.random(max_rand) * (Std.random(2) > 0 ? 1 : -1);
	}
/**
 *  Basic string hashing function, taken from java methods
 *	@param s String
 *	@return Int
 */
	private static function hashString(s:String) : Int {
		var h = 0;
		for (i in 0...s.length){  
			#if php
				untyped h = 31*h + __call__('ord',s.charAt(i));
				untyped{
					if (h > __php__('PHP_INT_MAX')) h %=__php__('PHP_INT_MAX') ;
					if (h < -__php__('PHP_INT_MAX')) h %=__php__('PHP_INT_MAX');
				}
			#else
				h = 31*h + s.charCodeAt(i); 
			#end
		}
		#if php
			h = untyped __call__('intval',h);
		#end
		return h;
	}

/**
 *  Basic Bool hashing function, taken from java methods
 *	@param b Bool
 *	@return Int
 */	
	private static function hashBool(b:Bool) : Int{
		return b ? 1231 : 1237;
	}
	
/**
 *  Reflect.fields() replacement that doesn't list the internal '__id' hash code field.
 *	@param o Dynamic
 *	@return Array<String>
 */
	public static function fields(o:Dynamic):Array<String>{
		var k = Reflect.fields(o);
		for (i in 0...k.length){
			if (k[i] == '__id'){
				k[i] = k[k.length-1];
				k.pop();
			}
		}
		return k;
	}
	


}

/**
 * IHashCode :	A "safe" way of specifying the presence of a hash code ID field
 *  for an arbitrary object.
 * 
 * @langversion		HaXe 2
 * @targets			cpp, flash, flash9, js, neko, php
 * @author			Justin Donaldson
 * @since			2010-02-05
 */
interface IHashCode
{
	private var __id:Int;
}